package org.exteen.exec;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.jexl2.JexlContext;
import org.exteen.ExteenException;
import org.exteen.lib.TagLibrary;
import org.exteen.parser.Attr;
import org.exteen.parser.Node;

public class ExteenContext implements JexlContext {

    protected final TopContext topContext;

    private ExteenContext parent;
    private Node node;

    private Tag tag;

    protected Map<String, Object> variables;

    public ExteenContext(ExteenContext parent, Node node) {
        this.topContext = parent.topContext;
        init(parent, node);
    }

    protected ExteenContext() { // constructor for topContext
        this.topContext = (TopContext) this;
        init(null, null);
    }

    protected final void init(ExteenContext parent, Node node) {
        this.parent = parent;
        this.node = node;
    }

    public void release() {
        this.parent = null;
        this.node = null;
        this.tag = null;
        if (this.variables != null) {
            this.variables.clear();
        }
        topContext.putCotextToPool(this);
    }

    public Node getNode() {
        return node;
    }

    public Tag getTagMark() {
        return tag;
    }

    public void markWithTag(Tag tag) {
        this.tag = tag;
    }

    public ExteenContext getParent() {
        return parent;
    }

    public boolean isDerivingVariables() {
        return true;
    }

    public ExteenContext createChildContext(Node node) {
        return topContext.createChildContext(this, node);
    }

    private Map<String, Object> getVarOwner(String varname) {
        if (variables!=null && variables.containsKey(varname)) {
            return variables;
        }
        if (!isDerivingVariables()) {
            return null;
        }
        if (parent==null) {
            return null;
        }
        return parent.getVarOwner(varname);
    }

    @Override
    public Object get(String varname) {
        if (variables!=null && variables.containsKey(varname)) {
            return variables.get(varname);
        }
        if (parent==null) {
            return null;
        }
        return parent.get(varname);
    }

    @Override
    public boolean has(String varname) {
        if (variables!=null && variables.containsKey(varname)) {
            return true;
        }
        if (parent==null) {
            return false;
        }
        return parent.has(varname);
    }

    @Override
    public void set(String varname, Object obj) {
        set(varname, obj, true);
    }

    public void set(String varname, Object obj, boolean override) {
        if (override) {
            Map<String, Object> varOwner = getVarOwner(varname);
            if (varOwner != null) {
                // override successful
                varOwner.put(varname, obj);
                return;
            }
        }
        // otherwise:
        if (variables == null) {
            variables = new HashMap<String, Object>();
        }
        variables.put(varname, obj);
    }

    public String toString() {
        return "" + variables;
    }

    public void mergeToParent() {
        if (this.variables != null) {
            if (parent.variables == null) {
                parent.variables = new HashMap<String, Object>();
            }
            parent.variables.putAll(this.variables);
        }
    }

    public void addLibrary(TagLibrary library) {
        topContext.addLibrary(library);
    }

    public Tag getTagForNode(Node node) {

        String namespace = node.getNamespace();
        String tagName = node.getTag();

        if (namespace==null) {
            return XmlTag.INSTANCE;
        }

        TagLibrary library = topContext.getLibrary(namespace);

        if (library==null) {
            return XmlTag.INSTANCE;
        }

        Tag tag = library.getTag(tagName);
        if (tag==null) {
            throw new RuntimeException("Tag " + tagName + " not found in " + namespace);
        }
        return tag;
    }

    // utility

    public String getVarAttrValue(String attrname) {
        return getVarAttrValue(node.getAttribute(attrname));
    }
    
    public String getVarAttrValue(Attr a) {

        if (a==null) {
            return null;
        }
        if (a.isExpression()) {
            throw new ExteenException("Value of " + a.getKey() + " attribute may not be an expression");
        }
        Object varval = a.getValue(null);
        if (varval == null) {
            varval = "";
        }
        String var = varval.toString().trim();

        if (!a.wasValidated()) {

            if (var.length()==0) {
                throw new ExteenException("Value of " + a.getKey() + " attribute may not empty");
            }

            if (!var.matches("[A-Za-z_][A-Za-z0-9_]*")) {
                throw new ExteenException("Value of " + a.getKey() + " attribute not acceptable: " + var);
            }
            // speed optimization
            a.validated();
        }

        return var;
    }

    public boolean parseAttributeAsBoolean(String attrname, boolean dflt) {

        Attr a = node.getAttribute(attrname);
        if (a==null) {
            return dflt;
        }
        Object av = a.getValue(this);
        if (av!=null) {
            if (av instanceof Boolean) {
                return (Boolean) av;
            } else {
                String strvalue = av.toString();
                if (strvalue.equalsIgnoreCase("true")
                        || strvalue.equalsIgnoreCase("yes")) {
                    return true;
                }
                if (strvalue.equalsIgnoreCase("false")
                        || strvalue.equalsIgnoreCase("no")) {
                    return false;
                }
            }
        }
        throw new ExteenException("Value of " + attrname + " attribute in " + node.getTag() + " tag must be of type boolean:" + av);
    }

    public long parseAttributeAsLong(String attrname, long dflt) {

        Attr a = node.getAttribute(attrname);
        if (a==null) {
            return dflt;
        }
        Object av = a.getValue(this);
        if (av!=null) {
            if (av instanceof Number) {
                return (Long) av;
            } else {
                try {
                    return Long.parseLong(av.toString());
                } catch (NumberFormatException nfex) {
                    throw new ExteenException("Value of " + attrname + " attribute in forEach must be of type long: " + av);
                }
            }
        } else {
            throw new ExteenException("Value of " + attrname + " attribute in forEach must be of type long");
        }
    }

//    public void doBody(ExteenStream stream) {
//        tag.doBody(this, stream);
//    }

    public void dump() {
        System.out.println(toString());
        if (parent != null) parent.dump();
    }

    public Tag findParentTagOfType(Class<? extends Tag> klass) {
        if (klass.isInstance(tag)) {
            return tag;
        }
        if (parent == null) {
            return null;
        }
        return parent.findParentTagOfType(klass);
    }

    public ExteenContext getParentOfType(Class<? extends ExteenContext> klass) {
        if (klass.isInstance(this)) {
            return this;
        }
        if (parent == null) {
            return null;
        }
        return parent.getParentOfType(klass);
    }
}
